using System;
using System.Collections;
using System.IO;
using CommandLine.Utility;
using Slaks.DocumentSerialization.Document;
using Slaks.DocumentSerialization.ParsedFiles;
using Slaks.GraphExtensions;
using Slaks.GraphExtensions.Algorithms;
using Slaks.GraphExtensions.Analysis;
using Slaks.GraphExtensions.GraphBuilder;
using Slaks.Graphs;
using Slaks.Graphs.Category;
using Slaks.Graphs.GraphBuilder;
using Slaks.Graphs.ShenkerGraph.Algorithms;
using Slaks.SuperGraphExtensions.Containers;

namespace TestShenkerGraph
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1
	{
		public Form1()
		{
		}

		private static void Main(String[] args)
		{
			TextWriter writer = null;
			Document2CategoryMap doc2CatMap = null;
			IList files = null;
			SuperGraphList graphList = new SuperGraphList();

			// Command line parsing
			Arguments commandLine = new Arguments(args);
			int numberOfVerticesToLeave = 0;

			const string flistParam = "fileList";
			const string graphSizeParam = "graphSize";
			const string categoryFileParam = "categoryFile";
			const string distanceAlgorithmParam = "distanceAlg";
			const string distanceAlgorithmValue1 = "MCS";
			const string distanceAlgorithmValue2 = "WGU";
			const string iterationNumberParam = "iterationNumber";
			const string outputFileParam = "outputFile";
			const string clusterNumberParam = "clusterNumber";
			const string representationType = "representationType";

			//schenker representation types
			const string standardRepresentationSchenker = "StandardSchenker";
			const string simpleRepresentationSchenker = "SimpleSchenker";
			const string nDistanceRepresentationSchenker = "NDistanceSchenker";
			const string nSimpleDistanceRepresentationSchenker = "NSimpleSchenker";
			const string rawFrequencyRepresentationSchenker = "RawFrequencySchenker";
			const string normalizedFrequencyRepresentationSchenker = "NormalizedFrequencySchenker";

			//slaks representation types
			const string standardRepresentationSlaks = "StandardSlaks";

			const string simpleRepresentationSlaks = "SimpleSlaks";
			const string nDistanceRepresentationSlaks = "NDistanceSlaks";
			const string nSimpleDistanceRepresentationSlaks = "NSimpleSlaks";
			const string rawFrequencyRepresentationSlaks = "RawFrequencySlaks";
			const string normalizedFrequencyRepresentationSlaks = "NormalizedFrequencySlaks";

			const string normalizedFrequencyUniformRepresentationSlaks = "NormalizedFrequencyUniformSlaks";

			const string nDistanceValue = "NDistanceValue";

			IRepresentationExtended representation = null;


			bool isCategory = false;
			int clusterNumber = 2;
			SuperGraphDistanceMeasure distanceMeasureDelegate = null;
			int numberOfIterations = 1;
			int nDistance = 1;
			int numberOfGraphs;

			BuilderType representationTypeValue;


			//add representation that the library supports
			AddRepresentation();


			try
			{
				#region output param (outputFile)

				if (commandLine[outputFileParam] == null) writer = Console.Out;
				else
				{
					try
					{
						writer = new StreamWriter(commandLine[outputFileParam], false);
					}
					catch (IOException)
					{
						if (writer != null) writer.Close();
						Console.WriteLine(commandLine[outputFileParam] + " couldn't be opened");
					}
				}

				#endregion

				#region file list param (fileList)

				if (commandLine[flistParam] != null)
				{
					string fileName = commandLine[flistParam];
					files = ParsedFiles.ReadXml(fileName);
					writer.WriteLine(files.Count + " files loaded from file " + commandLine[flistParam]);
				}
				else
				{
					writer.WriteLine("fileList parameter not found");
					return;
				}

				#endregion

				#region graph size param (graphSize)

				if (commandLine[graphSizeParam] != null)
				{
					try
					{
						numberOfVerticesToLeave = int.Parse(commandLine[graphSizeParam]);
					}
					catch (Exception)
					{
						writer.WriteLine("Check graphSizeParam value");
						return;
					}
				}
				else
				{
					writer.WriteLine("No graph node pruning considered");
				}

				#endregion

				#region category file param (categoryFile)

				if (commandLine[categoryFileParam] != null)
				{
					doc2CatMap = CategoryReader.ReadCategoryFile(commandLine[categoryFileParam]);
					writer.WriteLine("Category file " + commandLine[categoryFileParam] + " was loaded with " + doc2CatMap.Count + " categories");
					isCategory = true;
				}
				else
				{
					writer.WriteLine("No category file was provided, clustering analysis is not possible");
					isCategory = false;
				}

				#endregion

				#region iteration number param (iterationNumber)

				if (commandLine[iterationNumberParam] != null)
				{
					try
					{
						numberOfIterations = int.Parse(commandLine[iterationNumberParam]);
						if (numberOfIterations <= 0)
						{
							numberOfIterations = 1;
							writer.WriteLine("Number of iterations is set to 1");
						}
						else
						{
							writer.WriteLine("Number of iterations is set to " + numberOfIterations);
						}
					}
					catch (Exception)
					{
						writer.WriteLine("Value of " + iterationNumberParam + " incorrectly provided (must be integer)");
						return;
					}
				}
				else
				{
					numberOfIterations = 1;
					writer.WriteLine("No number of iterations provided, default is 1");
				}

				#endregion

				#region cluster param (clusterNumber)

				if (commandLine[clusterNumberParam] != null)
				{
					try
					{
						clusterNumber = int.Parse(commandLine[clusterNumberParam]);
					}
					catch (Exception)
					{
						writer.WriteLine("Cluster number value is invalid");
						return;
					}
				}
				else
				{
					clusterNumber = 1;
					writer.WriteLine("No cluster number value specified, default is 2");
				}

				#endregion

				#region Representations (Standard, Simple, NDistance, NSimple, RawFrequency, NormalizedFrequency)

				if (commandLine[representationType] != null)
				{
					if (commandLine[representationType] == standardRepresentationSchenker) representationTypeValue = BuilderType.StandardRepresentationSchenker;
					else if (commandLine[representationType] == simpleRepresentationSchenker) representationTypeValue = BuilderType.SimpleRepresentationSchenker;
					else if (commandLine[representationType] == nDistanceRepresentationSchenker) representationTypeValue = BuilderType.NDistanceRepresentationSchenker;
					else if (commandLine[representationType] == nSimpleDistanceRepresentationSchenker) representationTypeValue = BuilderType.NSimpleDistanceRepresentationSchenker;
					else if (commandLine[representationType] == rawFrequencyRepresentationSchenker) representationTypeValue = BuilderType.RawFrequencyRepresentationSchenker;
					else if (commandLine[representationType] == normalizedFrequencyRepresentationSchenker) representationTypeValue = BuilderType.NormalizedFrequencyRepresentationSchenker;

					else if (commandLine[representationType] == standardRepresentationSlaks) representationTypeValue = BuilderType.StandardRepresentationSlaks;
					else if (commandLine[representationType] == simpleRepresentationSlaks) representationTypeValue = BuilderType.SimpleRepresentationSlaks;
					else if (commandLine[representationType] == nDistanceRepresentationSlaks) representationTypeValue = BuilderType.NDistanceRepresentationSlaks;
					else if (commandLine[representationType] == nSimpleDistanceRepresentationSlaks) representationTypeValue = BuilderType.NSimpleDistanceRepresentationSlaks;
					else if (commandLine[representationType] == rawFrequencyRepresentationSlaks) representationTypeValue = BuilderType.RawFrequencyRepresentationSlaks;
					else if (commandLine[representationType] == normalizedFrequencyRepresentationSlaks) representationTypeValue = BuilderType.NormalizedFrequencyRepresentationSlaks;

					else if (commandLine[representationType] == normalizedFrequencyUniformRepresentationSlaks) representationTypeValue = BuilderType.NormalizedFrequencyUniformRepresentationSlaks;

					else throw new Exception("Representation type : " + commandLine[representationType] + " is invalid");

					representation = SuperGraphBuilderFactory.GetGraphBuilder(representationTypeValue);
					if (representation == null)
					{
						Console.WriteLine("No such representation " + representationTypeValue.ToString());
						throw new Exception("No such representation " + representationTypeValue.ToString());
					}
				}
				else
				{
					Console.WriteLine("No representation type");
					throw new Exception("No representation type");
				}

				#endregion

				#region distance algorithm param

				if (commandLine[distanceAlgorithmParam] != null)
				{
					if (commandLine[distanceAlgorithmParam] == distanceAlgorithmValue1)
					{
						if (representationTypeValue == BuilderType.StandardRepresentationSchenker || representationTypeValue == BuilderType.StandardRepresentationSlaks ||
							representationTypeValue == BuilderType.SimpleRepresentationSchenker || representationTypeValue == BuilderType.SimpleRepresentationSlaks ||
							representationTypeValue == BuilderType.NDistanceRepresentationSchenker || representationTypeValue == BuilderType.NDistanceRepresentationSlaks ||
							representationTypeValue == BuilderType.NSimpleDistanceRepresentationSchenker || representationTypeValue == BuilderType.NSimpleDistanceRepresentationSlaks)
							distanceMeasureDelegate = new SuperGraphDistanceMeasure(SuperGraphDistances.DistMCS4VertexEdgeCount);
						else
							distanceMeasureDelegate = new SuperGraphDistanceMeasure(SuperGraphDistances.DistMCS4VertexEdgeFrequency);

						writer.WriteLine("MCS distance algorithm set with representation : " + representationTypeValue.ToString());
					}
					else if (commandLine[distanceAlgorithmParam] == distanceAlgorithmValue2)
					{
						if (representationTypeValue == BuilderType.StandardRepresentationSchenker || representationTypeValue == BuilderType.StandardRepresentationSlaks ||
							representationTypeValue == BuilderType.SimpleRepresentationSchenker || representationTypeValue == BuilderType.SimpleRepresentationSlaks ||
							representationTypeValue == BuilderType.NDistanceRepresentationSchenker || representationTypeValue == BuilderType.NDistanceRepresentationSlaks ||
							representationTypeValue == BuilderType.NSimpleDistanceRepresentationSchenker || representationTypeValue == BuilderType.NSimpleDistanceRepresentationSlaks)
							distanceMeasureDelegate = new SuperGraphDistanceMeasure(SuperGraphDistances.DistWGU4VertexEdgeCount);
						else
							distanceMeasureDelegate = new SuperGraphDistanceMeasure(SuperGraphDistances.DistWGU4VertexEdgeFrequency);

						writer.WriteLine("WGU distance algorithm set with representation : " + representationTypeValue.ToString());
					}
						/*
					else if (commandLine[distanceAlgorithmParam] == distanceAlgorithmValue3)
					{
						if (representationTypeValue == BuilderType.StandardRepresentationSchenker || representationTypeValue == BuilderType.StandardRepresentationSlaks ||
							representationTypeValue == BuilderType.SimpleRepresentationSchenker || representationTypeValue == BuilderType.SimpleRepresentationSlaks ||
							representationTypeValue == BuilderType.NDistanceRepresentationSchenker || representationTypeValue == BuilderType.NDistanceRepresentationSlaks ||
							representationTypeValue == BuilderType.NSimpleDistanceRepresentationSchenker || representationTypeValue == BuilderType.NSimpleDistanceRepresentationSlaks)
							distanceMeasureDelegate = new SuperGraphDistanceMeasure(SlaksGraphDistances.DistMCS4VertexEdgeCount);
						else
							throw new Exception(representationTypeValue + " is not compatible with SlaksMCS distance measure");

						writer.WriteLine("SlaksMCS distance algorithm set");
					}
					else if (commandLine[distanceAlgorithmParam] == distanceAlgorithmValue4)
					{
						if (representationTypeValue == BuilderType.StandardRepresentationSchenker || representationTypeValue == BuilderType.StandardRepresentationSlaks ||
							representationTypeValue == BuilderType.SimpleRepresentationSchenker || representationTypeValue == BuilderType.SimpleRepresentationSlaks ||
							representationTypeValue == BuilderType.NDistanceRepresentationSchenker || representationTypeValue == BuilderType.NDistanceRepresentationSlaks ||
							representationTypeValue == BuilderType.NSimpleDistanceRepresentationSchenker || representationTypeValue == BuilderType.NSimpleDistanceRepresentationSlaks)
							distanceMeasureDelegate = new SuperGraphDistanceMeasure(SlaksGraphDistances.DistWGU4VertexEdgeCount);
						else
							throw new Exception(representationTypeValue + " is not compatible with SlaksWGU distance measure");

						writer.WriteLine("SlaksWGU distance algorithm set");
					}
					*/
					else
					{
						writer.WriteLine("Uknown distance algorithm : " + commandLine[distanceAlgorithmParam] + " -> Must be [MSC,WGU,SlaksMCS,SkaksWGU]");
						return;
					}

				}
				else
				{
					writer.WriteLine("No distance algorithm set");
					throw new Exception("No distance algorithm set");
				}

				#endregion

				#region Ndistance Value (NDistanceValue)

				if (representationTypeValue == BuilderType.NDistanceRepresentationSchenker || representationTypeValue == BuilderType.NDistanceRepresentationSlaks
					||
					representationTypeValue == BuilderType.NSimpleDistanceRepresentationSchenker || representationTypeValue == BuilderType.NSimpleDistanceRepresentationSlaks
					)
				{
					if (commandLine[nDistanceValue] != null)
					{
						nDistance = int.Parse(commandLine[nDistanceValue]);
					}
					else
					{
						writer.WriteLine("Distance value for NDistanceRepresentation[Slaks/Schenker] or NSimpleDistanceRepresentation[Slaks/Schenker] not provided, defaut to 1");
						nDistance = 1;
					}
				}

				#endregion

				writer.WriteLine("Parsing started at : " + DateTime.Now.ToString());

				SuperGraph newgraph = null;
				foreach (string s in files)
				{
					Document document = Xml2Document.ReadXml(s);

					if (representationTypeValue == BuilderType.NDistanceRepresentationSchenker ||
						representationTypeValue == BuilderType.NDistanceRepresentationSlaks ||
						representationTypeValue == BuilderType.NSimpleDistanceRepresentationSchenker ||
						representationTypeValue == BuilderType.NSimpleDistanceRepresentationSlaks
						)
					{
						newgraph = representation.Build(document, numberOfVerticesToLeave, nDistance);
					}
					else
					{
						newgraph = representation.Build(document, numberOfVerticesToLeave);
					}
					graphList.Add(newgraph);
				}

				writer.WriteLine("parsing complete at : " + DateTime.Now.ToString());

				if (doc2CatMap != null)
				{
					if (doc2CatMap.Count < graphList.Count)
					{
						writer.WriteLine("The category mapper size is less then the number of graphs");
						return;
					}
				}

				double dunnIndex;
				double mutualInfo;
				double randIndex;

				double dunnIndexAll = 0.0;
				double mutualInfoAll = 0.0;
				double randIndexAll = 0.0;

				numberOfGraphs = graphList.Count;

				double[] resultRand = new double[numberOfIterations];
				double[] resultDunn = new double[numberOfIterations];
				double[] resultMutual = new double[numberOfIterations];

				for (int j = 1; j <= numberOfIterations; j++)
				{
					writer.WriteLine("Iteration number " + j + " : Algorithm started at : " + DateTime.Now.ToString());
					SlaksGraphClustering slaks = new SlaksGraphClustering(graphList, clusterNumber, distanceMeasureDelegate);
					slaks.Apply();
					writer.WriteLine("Iteration number " + j + " : Algorithm complete at : " + DateTime.Now.ToString());

					if (j == numberOfIterations)
					{
						writer.WriteLine("\nCluster layout for the last iteration\n");
						writer.WriteLine("Total graphs : " + slaks.CountGraphs());

						for (int i = 0; i < slaks.GetSuperGraphClusters().Length; i++)
						{
							SuperGraph centroid = slaks.GetSuperGraphClusters()[i].GetCentroid();
							if (centroid == null) continue;
							string name = centroid.Name;
							writer.WriteLine("Centroid : " + name + "  category : " + doc2CatMap[name] + " , Num Graph : " + centroid.GetGraphs().Count);
							foreach (SuperGraph graph in slaks.GetSuperGraphClusters()[i])
							{
								name = graph.Name;
								writer.WriteLine("    " + name + "  category : " +
									doc2CatMap[name] + " , distance to centroid : " + distanceMeasureDelegate(centroid, graph));
								writer.WriteLine("      number of graphs : " + graph.GetGraphs().Count);
								writer.WriteLine("");
							}
							writer.WriteLine("\n");
						}
					}

					//fill in categories of the graphs
					if (j == 1)
					{
						string category;
						foreach (SuperGraph graph in graphList)
						{
							category = doc2CatMap[graph.Name];
							graph.Category = category;
						}
					}


					randIndex = ClusteringAnalysis.GetRandIndex(slaks.GetSuperGraphClusters(), null);
					dunnIndex = ClusteringAnalysis.GetDunnIndex(slaks.GetSuperGraphClusters(), distanceMeasureDelegate);
					mutualInfo = ClusteringAnalysis.GetMutualInformation(slaks.GetSuperGraphClusters(), null, CategoryReader.GetCategoryList(), numberOfGraphs);

					resultRand[j - 1] = randIndex;
					resultDunn[j - 1] = dunnIndex;
					resultMutual[j - 1] = mutualInfo;

					writer.WriteLine("Rand Index = " + randIndex);
					writer.WriteLine("Dunn Index = " + dunnIndex);
					writer.WriteLine("Mutual Information = " + mutualInfo);

					randIndexAll += randIndex;
					dunnIndexAll += dunnIndex;
					mutualInfoAll += mutualInfo;

					Console.WriteLine("Iteration number " + j + " ended");

				}

				if (numberOfIterations > 1)
				{
					if (randIndexAll != 0.0) randIndexAll /= numberOfIterations;
					if (dunnIndexAll != 0.0) dunnIndexAll /= numberOfIterations;
					if (mutualInfoAll != 0.0) mutualInfoAll /= numberOfIterations;

					double randDeviation = GetStandardDeviation(resultRand, randIndexAll);
					double dunnDeviation = GetStandardDeviation(resultDunn, dunnIndexAll);
					double mutualDeviation = GetStandardDeviation(resultMutual, mutualInfoAll);

					writer.WriteLine("\nAverage cluster measures");
					writer.WriteLine("Rand Index Average : " + randIndexAll);
					writer.WriteLine("Dunn Index Average : " + dunnIndexAll);
					writer.WriteLine("Mutual Information Average : " + mutualInfoAll);

					writer.WriteLine("\n");

					writer.WriteLine("Rand index standard deviation : " + randDeviation);
					writer.WriteLine("Dunn Index standard deviation : " + dunnDeviation);
					writer.WriteLine("Mutual Information standard deviation : " + mutualDeviation);

					writer.WriteLine("\n");
				}

				if (isCategory)
				{
					writer.WriteLine("\nCategories : ");
					writer.WriteLine("Total categories : " + CategoryReader.GetCategoryList().Count);
					IList l = CategoryReader.GetCategoryList();
					for (int i = 0; i < l.Count; i++) writer.WriteLine(l[i]);
				}


			}
			catch (Exception ex)
			{
				writer.WriteLine(ex.Message + Environment.NewLine);
				if (ex.InnerException != null) writer.WriteLine(ex.InnerException.Message);
				writer.WriteLine(Environment.NewLine + ex.StackTrace);
			}
			finally
			{
				//close stream if the stream is not Console.Out
				if (commandLine[outputFileParam] != null) writer.Close();
			}
		}

		private static void AddRepresentation()
		{
			//slaks representation
			SuperGraphBuilderFactory.AddGraphBuilder(BuilderType.StandardRepresentationSlaks, new StandardRepresentationExtendedSlaks());
		}

		private static void Distances(SuperGraphList graphList)
		{
			int i = 1;
			foreach (SuperGraph graph1 in graphList)
			{
				Console.WriteLine((i++).ToString());
				foreach (SuperGraph graph2 in graphList)
				{
					Console.WriteLine(graph1.Name + " " + graph2.Name + " " + SuperGraphDistances.DistMCS4VertexEdgeCount(graph1, graph2));
				}

				Console.WriteLine("\n");
			}
		}

		private static double GetStandardDeviation(double[] results, double average)
		{
			double deviation = 0.0;
			for (int i = 0; i < results.Length; i++)
			{
				deviation += ((results[i] - average)*(results[i]*average));
			}

			if (results.Length == 1) return Math.Sqrt(deviation);

			deviation /= (results.Length - 1);

			deviation /= Math.Sqrt(deviation);

			return deviation;
		}

		private static void Connections(GraphList graphList, string graphName)
		{
			Graph graph = null;
			foreach (Graph graph2 in graphList)
			{
				if (graph2.Name == graphName)
				{
					graph = graph2;
					break;
				}
			}

			if (graph == null)
			{
				Console.WriteLine(graphName + " not found");
				return;
			}

			foreach (Vertex vertex in graph.GetVertexList())
			{
				SectionType2EdgeListMap s2m = vertex.GetSection2EdgeMap();

				Console.WriteLine(vertex.GetId() + " ( frequency " + vertex.Frequency + " )");

				ICollection sectionKey = s2m.Keys;

				foreach (string sectionType in sectionKey)
				{
					EdgeList edgeList = s2m[sectionType];
					Console.WriteLine(sectionType + " : ");
					foreach (Edge edge in edgeList)
					{
						Console.WriteLine(edge.GetTarget().GetId() + "  ( frequency" + edge.GetFrequency() + " ) " + edge.GetSectionType());
					}
				}
				Console.WriteLine("\n");
			}
		}
	}
}